﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Dynamic;
using System.Linq;
using System.Xml.Linq;

namespace Isuka.Xml
{
    /// <summary>
    /// Attribute Lookup
    /// </summary>
    public class DynamicXAttributes
        : DynamicObject
    {
        readonly XElement el;

        public DynamicXAttributes(XElement element)
        {
            Contract.Requires<ArgumentNullException>(element != null, "element");

            el = element;
        }

        [ContractInvariantMethod]
        void ObjectInvariant()
        {
            Contract.Invariant(el != null);
        }

        #region Overrides DynamicObject

        public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
        {
            Contract.Ensures(Contract.ValueAtReturn(out result) != null);

            Contract.Assume(binder != null);
            Contract.Assume(!string.IsNullOrEmpty(binder.Name));

            var attributes = el.Attributes(binder.Name);
            var attrValues = attributes.Select(x => x.Value);
            result = attrValues;
            return true;
        }

        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            Contract.Assume(binder != null);

            if (binder.Type == typeof(XAttribute[]))
            {
                result = el.Attributes().ToArray();
                return true;
            }
            else if (binder.Type == typeof(IEnumerable<XAttribute>)
                  || binder.Type == typeof(IEnumerable))
            {
                result = el.Attributes();
                return true;
            }
            else
            {
                return base.TryConvert(binder, out result);
            }
        }

        public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result)
        {
            Contract.Assume(binder != null);

            switch (binder.Name)
            {
                case "GetEnumerator":
                    result = el.Attributes().GetEnumerator();
                    return true;
            }
            return base.TryInvokeMember(binder, args, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            Contract.Assume(binder != null);
            Contract.Assume(!string.IsNullOrEmpty(binder.Name));

            switch (binder.Name)
            {
                case "GetEnumerator":
                    return false;
            }
            try
            {
                el.SetAttributeValue(binder.Name, value);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public override string ToString()
        {
            return el.Name.ToString() + ".Attributes";
        }

        #endregion
    }
}
